﻿// (c)2010 David Schwarz (david.schwarz@live.com)
// This file is licensed under GNU GPL v2 license, see more at http://www.gnu.org/licenses/gpl-2.0.html
namespace Z2.Core
{

	#region Using

	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.IO;
	using System.Xml;
	using Microsoft.Xna.Framework;
	using Microsoft.Xna.Framework.Graphics;
	using Z2.Language;
	using Z2.Common;
	using System.Reflection;
    using System.IO.Compression;
using Z2.Scripting;
	using System.Text;

	#endregion

	/// <summary>
	/// Třída pro mapu
	/// </summary>
	public class World
	{

		#region Consts

		internal const string WORLD_DIR = "Maps";
		private const string DEFINITION_DIR = @"Objects\{0}.xml";
		
		#endregion

		#region Members

		private static bool _isLoading;
		private static World _currentWorld;
		private static Dictionary<string, FieldDefinition> _definitions;
		private static readonly List<Type> _typeCache = new List<Type>();

		private string m_fileName;
		private string m_name;
		private string m_author;

		private float m_globalLight;
		private float m_globalLightTo;
		private float m_globalLightStep;
		private Color m_globalColorLight = new Color(0.0f, 0.0f, 0.0f, 1.0f);

		private Rectangle m_bounds;

		private Field[,] m_field;
		
		private List<WorldField> m_foreground;
		private List<ObjectBase> m_objects;
		private List<ObjectBase> m_objectsOnScreen;
		private List<EffectBase> m_effects;
		private List<IDraw> m_drawables;

		private Vector2 m_playerStart;

		private Player m_player;
		private static ScriptingEngine _scriptingEngine;
		private MapScript m_script;
		//private List<Background> m_backgrounds;
		private List<StaticLight> m_staticLights;

		private EffectUnderWater m_water;

		//private Background m_daybackground;
		private Sun m_sun;

		private static ParticleSettings _waterSplashSettings;

		#endregion
		
		#region Constructor

		/// <summary>
		/// Statický konstruktor
		/// </summary>
		static World()
		{
			_definitions = new Dictionary<string, FieldDefinition>();
			_isLoading = true;

			// load assembly types first
			Assembly assembly = Assembly.GetExecutingAssembly();
			_typeCache = new List<Type>(assembly.GetTypes());
			_typeCache.RemoveAll(match => !World.IsDerived(match));

			// load definitions
			string fileName = string.Format(World.DEFINITION_DIR, "Definitions");
			if (!File.Exists(fileName))
			{
				throw new FileNotFoundException(Strings.DefinitionFileNotFound, "Definitions.xml");
			}

			XmlDocument document = new XmlDocument();
			document.Load(fileName);
			XmlNode definitionsNode = document.GetElementsByTagName("definitions")[0];
			World.ExploreNode(definitionsNode);

			_waterSplashSettings = new ParticleSettings();
			_waterSplashSettings.Power = new Vector2(10, 10);
			_waterSplashSettings.Type = ParticleType.Water;
			_waterSplashSettings.LifeTime = new Vector2(500);
			_waterSplashSettings.HasLight = false;
			_waterSplashSettings.Ammount = 10;
		}

		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="fileName">Název souboru mapy (bez přípony)</param>
		public World(string fileName)
		{
			this.m_fileName = fileName;
		}

		#endregion

		#region Properties

		private ScriptingEngine ScriptingEngine
		{
			get
			{
				if (_scriptingEngine == null)
					_scriptingEngine = new ScriptingEngine();
				return _scriptingEngine;
			}
		}

		/// <summary>
		/// Vrací název mapy
		/// </summary>
		public string Name
		{
			get
			{
				return this.m_name;
			}
		}

		/// <summary>
		/// Vrací jméno autora
		/// </summary>
		public string Author
		{
			get
			{
				return this.m_author;
			}
		}

		/// <summary>
		/// Vrací seznam použitých efektů v tomto levelu
		/// </summary>
		public List<EffectBase> Effects
		{
			get
			{
				return this.m_effects;
			}
		}

		/// <summary>
		/// Vrací hranice mapy
		/// </summary>
		public Rectangle Bounds
		{
			get
			{
				return this.m_bounds;
			}
		}

		public static bool IsLoading
		{
			get
			{
				return _isLoading;
			}
		}

		/// <summary>
		/// Vrací aktuální mapu
		/// </summary>
		public static World Current
		{
			get
			{
				return _currentWorld;
			}
		}

		/// <summary>
		/// Hráč
		/// </summary>
		public Player Player
		{
			get
			{
				return this.m_player;
			}
		}

		public Color GlobalColorLight
		{
			get { return this.m_globalColorLight; }
		}

		public float GlobalLightTo
		{
			get { return this.m_globalLightTo; }
		}

		public float GlobalLightStep
		{
			get { return this.m_globalLightStep; }
		}

		public float GlobalLight
		{
			get { return this.m_globalLight; }
			set
			{
				this.m_globalLight = value;
				
				this.m_globalColorLight = new Color(
					value, 
					value, 
					value, 1.0f);
			}
		}

		#endregion

		#region Methods

		/// <summary>
		/// Recursive method that explores all xml nodes and loads definitions from them
		/// </summary>
		/// <param name="rootNode"><see cref="XmlNode"/></param>
		private static void ExploreNode(XmlNode rootNode)
		{
			foreach (XmlNode node in rootNode)
			{
				if (node.Attributes.Count == 0)
				{
					World.ExploreNode(node);
					continue;
				}
				else if (node.Attributes["Name"] == null)
				{
					Debug.WriteLine("Skipping " + node.Name + ", attribute Name not found.");
					continue;
				}

				FieldDefinition definition = new FieldDefinition(node);
				string name = Convert.ToString(node.Attributes["Name"].Value).ToLower();
				if (_definitions.ContainsKey(name))
				{
					throw new Exception(name + " is already in definitions!");
				}
				_definitions.Add(name, definition);
			}
		}

		public bool ContainsItem(Vector2 p)
		{
			int x = (int)p.X / WorldField.SIZE;
			int y = (int)p.Y / WorldField.SIZE;
			if (x < 0 || y < 0 || x >= this.m_bounds.Width || y >= this.m_bounds.Height) return true;
			
			//return this.m_field[x, y] != null;

			bool containsField = this.m_field[x, y] != null;
			if (containsField) return true;
			Rectangle r = new Rectangle((int)p.X, (int)p.Y, 1, 1);
			foreach (ObjectBase obj in this.m_objects)
			{
				Rectangle rect = obj.GetRectangle(0, 0);
				rect.Inflate(-rect.Width / 10, -rect.Height / 10);
				if (r.Intersects(rect)) return true;
			}
			return false;
		}


		public void TurnLight(float step, float to)
		{
			this.m_globalLightTo = to;
			this.m_globalLightStep = step;
		}

		public ObjectBase Find(int id)
		{
			ObjectBase found = this.m_objects.Find(match => match.Id == id);
			if (found == null)
			{
				throw new Exception(string.Format("Object with id {0} was not found.", id));
			}
			return found;
		}
		
		public void Update()
		{
			if(IsLoading) return;
			Rectangle screen = Camera.Instance.Screen;
			screen.Width = (screen.Width - screen.X) * WorldField.SIZE;
			screen.Height = (screen.Height - screen.Y) *  WorldField.SIZE;
			screen.X *= WorldField.SIZE;
			screen.Y *= WorldField.SIZE;
			
			this.m_objectsOnScreen.Clear();
			for(int i = this.m_objects.Count -1; i>=0;--i)
			{
				ObjectBase obj = this.m_objects[i];
				obj.Update();
				if (screen.Intersects(obj.GetRectangle(0, 0))) this.m_objectsOnScreen.Add(obj);
			}

			if (this.m_globalLight != this.m_globalLightTo && this.m_globalLightStep > 0)
			{
				if (this.m_globalLightTo > this.m_globalLight)
				{
					this.GlobalLight += this.m_globalLightStep;
					if (this.m_globalLight > this.m_globalLightTo) this.m_globalLight = this.m_globalLightTo;
				}
				else
				{
					this.GlobalLight -= this.m_globalLightStep;
					if (this.m_globalLight < this.m_globalLightTo) this.m_globalLight = this.m_globalLightTo;
				}
				//this.m_daybackground.Sprite.Visibility = this.m_globalLight;
			
            }

            for (int i = this.m_drawables.Count - 1; i >= 0; --i)
            {
                this.m_drawables[i].Update();
            }
			
			
			if (this.m_script != null) this.m_script.Entry();

            foreach (StaticLight light in this.m_staticLights)
                light.Update();

			ParticleEngine.Instance.Update();
		}

		/// <summary>
		/// Metoda uvolní paměť mapy
		/// </summary>
		public void Release()
		{
			SpriteManager.Instance.ReleaseGroup(World.WORLD_DIR);

			if (this.m_field != null && this.m_field.Length > 0)
			{
				GC.SuppressFinalize(this.m_field);
			}

			GC.WaitForPendingFinalizers();

			this.m_field = null;

			if (this.m_drawables == null)
			{
				this.m_drawables = new List<IDraw>();
			}
			else this.m_drawables.Clear();

			if (this.m_objectsOnScreen == null)
			{
				this.m_objectsOnScreen = new List<ObjectBase>();
			}
			else this.m_objectsOnScreen.Clear();

			if (this.m_staticLights == null)
			{
				this.m_staticLights = new List<StaticLight>();
			}
			else this.m_staticLights.Clear();

			//if (this.m_backgrounds == null)
			//{
			//    this.m_backgrounds = new List<Background>();
			//}
			//else this.m_backgrounds.Clear();

			if (this.m_objects == null)
			{
				this.m_objects = new List<ObjectBase>();
			}
			else this.m_objects.Clear();

			if (this.m_effects == null)
			{
				this.m_effects = new List<EffectBase>();
			}
			else this.m_effects.Clear();

			if (this.m_foreground == null)
			{
				this.m_foreground = new List<WorldField>();
			}
			else this.m_foreground.Clear();
		}

		public List<ObjectBase> GetSectorsObjects(ObjectBase objectBase, CanCollideDelegate CanCollide)
		{
			List<ObjectBase> result = new List<ObjectBase>();
			Rectangle rect = objectBase.GetRectangle(0, 0);

			rect.Inflate(WorldField.SIZE, WorldField.SIZE);
			foreach (ObjectBase obj in this.m_objects)
			{
				if (obj.CantCollide(objectBase)) continue;
				if (CanCollide(objectBase, obj)) continue;
				if (obj.GetRectangle(0, 0).Intersects(rect))
					result.Add(obj);
			}
			return result;
		}

		public void UnregisterObject(ObjectBase objectBase)
		{
			this.m_objects.Remove(objectBase);
		}

		/// <summary>
		/// Metoda zaregistruje objekt do mapy a sektoru
		/// </summary>
		/// <param name="objectBase"><see cref="ObjectBase"/> nebo potomek</param>
		public void RegisterObject(ObjectBase objectBase)
		{
			if (objectBase is Player)
			{
				this.m_objects.Insert(this.m_objects.Count, objectBase);
				objectBase.Position = this.m_playerStart;
				this.m_player = (Player)objectBase;
				this.m_player.Initialize();
			}
			else
			{
				objectBase.Initialize();
				this.m_objects.Add(objectBase);
			}
		}

		/// <summary>
		/// Metoda načte definici z XML a uloží ji do cache.
		/// </summary>
		/// <param name="name">Název definice</param>
		/// <returns><see cref="FieldDefinition"/></returns>
		private static FieldDefinition LoadDefinition(string name)
		{
			if (_definitions.ContainsKey(name))
			{
				return _definitions[name];
			}

			throw new NotImplementedException(name + " definition not found!");
		}

		/// <summary>
		/// Metoda načte pole mapy podle definice
		/// </summary>
		/// <param name="name">Název definice</param>
		/// <returns><see cref="WorldField"/></returns>
		private WorldField LoadFieldByDefinition(string name)
		{
			FieldDefinition definition = World.LoadDefinition(name);
			return new WorldField(definition);
		}

		private static bool IsDerived(Type type)
		{
			if (type == null) return false;
			if (type.BaseType == typeof(ObjectBase)) return true;
			return IsDerived(type.BaseType);
		}

		/// <summary>
		/// Metoda načte objekt podle jeho definice
		/// </summary>
		/// <param name="name">Název definice objektu</param>
		/// <returns><see cref="ObjectBase"/></returns>
		private ObjectBase LoadObjectByDefintion(string name)
		{
			FieldDefinition definition = World.LoadDefinition(name);
			ObjectBase objectBase = null;
			Type type = _typeCache.Find(match => string.Compare(match.Name, name, true) == 0);

			if (type != null)
			{
				objectBase = Activator.CreateInstance(type) as ObjectBase;
			}

			if (objectBase == null || type == null)
			{
				throw new NotImplementedException(string.Format(Strings.ObjectNotSupported, name));
			}

			return objectBase;
		}

		/// <summary>
		/// Metoda vloží do mapy políčko <paramref name="field"/>
		/// </summary>
		/// <param name="draw">Určuje, kde se má dané políčko vykreslovat</param>
		/// <param name="field">Políčko mapy</param>
		/// <param name="x">X</param>
		/// <param name="y">Y</param>
		private void Put(WorldFieldDraw draw, WorldField worldField, int x, int y)
		{
			Field field = null;
			if (this.m_field[x, y] == null)
			{
				field = new Field();
				field.Position = new Vector2(x * WorldField.SIZE, y * WorldField.SIZE);
				this.m_field[x, y] = field;
			}
			else
			{
				field = this.m_field[x, y];
			}


			worldField.IsFront = (draw == WorldFieldDraw.Foreground);

			if (worldField.IsFront)
			{
				field.Foreground = worldField;
			}
			else
			{
				field.Background = worldField;
			}
		}

        public void AddDraw(IDraw draw)
        {
            if (draw == null) return;
            if (this.m_drawables.Contains(draw)) return;
            this.m_drawables.Add(draw);
        }

        public void RemoveDraw(IDraw draw)
        {
            if (draw == null) return;
            if (!this.m_drawables.Contains(draw)) return;
            this.m_drawables.Remove(draw);
        }

		public void AddLight(StaticLight light)
		{
			this.m_staticLights.Add(light);
		}

		/// <summary>
		/// Metoda načte data mapy
		/// </summary>
		/// <remarks>
		/// Upravit načítání objektů a políček tak, aby se zbytečně neduplikoval kód načítání
		/// společných atributů.
		/// </remarks>
		public void Load()
		{
			_currentWorld = this; // Uložení ref. na akt. mapu
			_isLoading = true;
			Stopwatch watch = new Stopwatch();
			watch.Start();
			// Nejdřív uvolníme zabranou paměť
			this.Release();

			string fileName = World.GetFullPath(this.m_fileName);
			if (!File.Exists(fileName))
			{
				throw new FileNotFoundException(Strings.MapFileNotFound, fileName);
			}

			XmlDocument document = new XmlDocument();
			using (Stream stream = File.OpenRead(fileName))
			using (GZipStream gzip = new GZipStream(stream, CompressionMode.Decompress))
			{
				document.Load(gzip);
			}

			// Nejdříve načteme hranice
			XmlNode mapNode = document.GetElementsByTagName(XmlConst.ELM_MAP)[0];
			this.m_bounds = new Rectangle();
			this.m_bounds.Width = Convert.ToInt32(mapNode.Attributes[XmlConst.ATT_BOUNDW].Value);
			this.m_bounds.Height = Convert.ToInt32(mapNode.Attributes[XmlConst.ATT_BOUNDH].Value);
			this.m_bounds.X = Convert.ToInt32(mapNode.Attributes[XmlConst.ATT_BOUNDX].Value);
			this.m_bounds.Y = Convert.ToInt32(mapNode.Attributes[XmlConst.ATT_BOUNDY].Value);


			// Alokujeme potřebnou paměť
			this.m_field = new Field[this.m_bounds.Width, this.m_bounds.Height]; // Prostředek

			this.m_playerStart = new Vector2( // Startovní pozice hráče
				Convert.ToSingle(mapNode.Attributes[XmlConst.ATT_STARTX].Value) * WorldField.SIZE,
				Convert.ToSingle(mapNode.Attributes[XmlConst.ATT_STARTY].Value) * WorldField.SIZE);

			// Let's do it parallel way!
			ParallelManager manager = new ParallelManager();
			this.ScriptingEngine.Clear();

			// Načteme pozadí
			XmlNodeList backgrounds = document.GetElementsByTagName(XmlConst.ELM_BACKGROUND);
			if (backgrounds.Count > 0)
			{
				foreach (XmlNode node in backgrounds)
				{
					string asset = node.Attributes[XmlConst.ATT_NAME].Value;
					asset = asset.Substring(0, asset.Length - 4);
					Background background = new Background(asset);
					this.m_drawables.Add(background);
				}
			}

			// Default light state
			this.GlobalLight = 1F;
			// EXPERIMENTS

			Background daybackground = new Background("day");
			daybackground.Sprite.Depth = Depths.Background + 0.01F;
			daybackground.IsDay = true;

			this.m_sun = new Sun();
			this.TurnLight(0.1F, 0.3F);
			this.m_drawables.Add(this.m_sun);
			this.m_drawables.Add(daybackground);

			// END-EXPERIMENTS

			//
			// Zkusíme načíst & zkompilovat skript (paralelně)
			//
			manager.Create(delegate
			{
				XmlNodeList scriptList = document.GetElementsByTagName(XmlConst.ELM_SCRIPT);
				if (scriptList.Count > 0)
				{
					string data = scriptList[0].InnerText;
					byte[] sourceData = Convert.FromBase64String(data);
					string source = Encoding.Unicode.GetString(sourceData);
					if (string.Compare(source.Trim(), string.Empty) == 0) return;
					try
					{
						Guid id = this.ScriptingEngine.AddScript(source);
						this.m_script = (MapScript)this.ScriptingEngine.GetScript(id);
					}
					catch (ScriptingEngineException ex)
					{
						Debug.WriteLine(ex);
					}
				}
			});

			//
			// Načteme pole mapy
			//
			manager.Create(delegate
			{
				string definition = string.Empty;
				int x, y;
				XmlNodeList fieldList = document.GetElementsByTagName(XmlConst.ELM_FIELD);
				foreach (XmlNode fieldNode in fieldList)
				{
					definition = fieldNode.Attributes[XmlConst.ATT_DEFINITION].Value;
					x = Convert.ToInt32(fieldNode.Attributes[XmlConst.ATT_X].Value);
					y = Convert.ToInt32(fieldNode.Attributes[XmlConst.ATT_Y].Value);

					FieldDefinition def = World.LoadDefinition(definition);
					WorldField field = this.LoadFieldByDefinition(definition);
					//field.Position = new Vector2(x*WorldField.SIZE, y*WorldField.SIZE);
					this.Put(def.Draw, field, x, y);
				}
			});

			//
			// Načteme objekty
			//
			manager.Create(delegate
			{
				string definition = string.Empty;
				long id;
				int x, y;
				XmlNodeList objectList = document.GetElementsByTagName(XmlConst.ELM_OBJECT);
				foreach (XmlNode objectNode in objectList)
				{
					ObjectBehaviorBase behavior = null;
					id = Convert.ToInt64(objectNode.Attributes[XmlConst.ATT_ID].Value);
					definition = objectNode.Attributes[XmlConst.ATT_DEFINITION].Value;
					x = Convert.ToInt32(objectNode.Attributes[XmlConst.ATT_X].Value);
					y = Convert.ToInt32(objectNode.Attributes[XmlConst.ATT_Y].Value);

					ObjectBase mapObject = this.LoadObjectByDefintion(definition);
					mapObject.Id = id;
					mapObject.Position = new Vector2(x*WorldField.SIZE, y*WorldField.SIZE);
					mapObject.InitialPosition = new Vector2(x*WorldField.SIZE, y*WorldField.SIZE);
					mapObject.Type = (ObjectType) Convert.ToInt32(objectNode.Attributes[XmlConst.ATT_TYPE].Value);

					if (mapObject.Type == ObjectType.MoveableNoGravity)
					{
						behavior = new MoveableBehavior();
						((MoveableBehavior)behavior).Move = Convert.ToInt32(objectNode.Attributes[XmlConst.ATT_MOVE].Value);
					}

					if (mapObject.Type == ObjectType.Moveable)
					{
						behavior = new GravityBehavior();
					}

					if (mapObject.Type == ObjectType.Bendable)
					{
						behavior = new BendableBehavior();
						((BendableBehavior) behavior).Bend = Convert.ToInt32(objectNode.Attributes[XmlConst.ATT_BEND].Value);
					}

					mapObject.Behavior = behavior;
					this.RegisterObject(mapObject);
				}
			});

			manager.Wait();

			
			if (this.m_script != null)
			{
				Debug.WriteLine("Initializing script..");
				this.m_script.World = this;
				this.m_script.Initialize();
			}
			_isLoading = false;

			watch.Stop();
			Debug.WriteLine(string.Format("Map {0} loaded. It took {1}ms.", 
				string.IsNullOrEmpty(this.m_name)? "unnamed" : this.m_name, 
				watch.ElapsedMilliseconds));

			this.m_water = new EffectUnderWater(SpriteManager.Instance.Content, SpriteManager.Instance.Graphics);
		}

		public void DrawLight(SpriteBatch batch)
		{
			ParticleEngine.Instance.DrawLight(batch);
			//this.m_sun.DrawLight(batch);

            for (int i = this.m_drawables.Count - 1; i >= 0; --i)
            {
                this.m_drawables[i].DrawLight(batch);
            }

			foreach (StaticLight light in this.m_staticLights)
			{
				light.DrawLight(batch);
			}

			foreach (ObjectBase obj in this.m_objectsOnScreen)
			{
				obj.DrawLight(batch);
			}
		}

		/// <summary>
		/// Vykreslení aktuální obrazovky mapy
		/// </summary>
		/// <param name="batch"><see cref="SpriteBatch"/></param>
		public void Draw(SpriteBatch batch)
		{
			// Získáme kopii aktuální pozice (tohle je celkem důležité,
			// jinak by docházelo k "trhání" obrazu).
			Camera camera = Camera.Instance;
			
			Field field = null;


            for (int i = this.m_drawables.Count - 1; i >= 0; --i)
            {
                this.m_drawables[i].Draw(batch);
            }

			camera.Lock();
			
			// Vykreslujeme jen to, co je vidět (hranice získáme z kamery)
			for (int i = camera.Screen.X; i < camera.Screen.Width; ++i)
			for (int j = camera.Screen.Y; j < camera.Screen.Height; ++j)
			{
				if (j >= this.m_bounds.Height) continue;
				if (i >= this.m_bounds.Width) continue;
				field = this.m_field[i, j];
				if (field == null) continue;
				
				field.Draw(batch, Vector2.Add(field.Position, camera.Position));
				if (field.ContainsWater) this.m_water.Rects.Add(new Rectangle(
					 (int)(field.Position.X + Camera.Instance.Position.X),
					 (int)(field.Position.Y + Camera.Instance.Position.Y), 32, 32));
			}

			foreach (ObjectBase objBase in this.m_objectsOnScreen)
			{
				objBase.Draw(batch);
			}

			ParticleEngine.Instance.Draw(batch, camera.Position);

			camera.Unlock();
		}

		public void DrawWater(SpriteBatch batch, GameTime gameTime)
		{
			this.m_water.Draw(batch, gameTime);
			//this.m_waterAreas.Clear();
		}

		/// <summary>
		/// Metoda získá celou cestu k souboru mapy
		/// </summary>
		/// <param name="fileName">Název souboru mapy, bez přípony.</param>
		/// <returns>Celá cesta</returns>
		private static string GetFullPath(string fileName)
		{
			return string.Format("{0}/{1}.zmap", World.WORLD_DIR, fileName);
		}

		internal IntersectionResult Intersects(ObjectBase obj, Rectangle o, bool vertical)
		{
			int left = (o.X / WorldField.SIZE);
			int top = (o.Y / WorldField.SIZE);
			int right = (o.X + o.Width) / WorldField.SIZE;
			int bottom = (o.Y + o.Height) / WorldField.SIZE;
			++bottom;
			++right;

			Vector2 p;
			bool water = false;
			IntersectionResult result = null;

			for(int i = left; i < right;++i)
			for (int j = top; j < bottom; ++j)
			{
				if (i < this.m_bounds.X || j < this.m_bounds.Y ||
					i >= this.m_bounds.Width || j >= this.m_bounds.Height)
				{
					continue;
				}

				Field field = this.m_field[i, j];
				if (field != null && vertical && field.ContainsWater && !(obj is ParticleBasic))
				{
					water = true;
				}
				else water = false;
				if (field == null || !field.IsSolid) continue;
				
				p = field.Position;

				if (result == null & ((o.X + o.Width) > p.X && o.X < (p.X + WorldField.SIZE) &&
					 (o.Y + o.Height) > p.Y && o.Y < (p.Y + WorldField.SIZE)))
				{
					Rectangle rect = new Rectangle(
						(int)field.Position.X, 
						(int)field.Position.Y, 
						WorldField.SIZE, 
						WorldField.SIZE);
					result = new IntersectionResult();
					result.IsCollision = true;
					result.HorizontalType = (o.Center.X > rect.Center.X) ? IntersectionHorizontalType.FromRight : IntersectionHorizontalType.FromLeft;
					result.VerticalType = (o.Center.Y > rect.Center.Y) ? IntersectionVerticalType.FromBottom : IntersectionVerticalType.FromTop;
					result.Rectangle = rect;

					if (!vertical && this.CanGoUp(obj, result.HorizontalType, o))
					{
						// We can go one Field up
						result = IntersectionResult.None; // set intersection result to none
						i = right; // break second loop
						water = false;
						break; // break first loop
					}
				}
			}
			if (vertical && !water) obj.IsInWater = false;
			if (vertical && water && !obj.IsInWater)
			{
				if (obj.Move.Y > 10)
				{
					ParticleEngine.Instance.Create(
						Vector2.Add(obj.Position, new Vector2(obj.Sprite.FrameWidth / 2, obj.Sprite.FrameHeight)),
						_waterSplashSettings);
				}
				obj.IsInWater = true;
			}
			

			return result == null ? IntersectionResult.None : result;
		}

		private bool CanGoUp(ObjectBase obj, IntersectionHorizontalType horizontal, Rectangle o)
		{
			if (obj.VerticalState != ObjectStateVertical.Idle) return false;
			if (obj is ParticleBasic) return false;

			int x = horizontal == IntersectionHorizontalType.FromLeft ? o.Right : o.Left;
			int y = (o.Bottom/WorldField.SIZE) - 2; 
			x /= WorldField.SIZE;

			bool result = (this.m_field[x, y] == null || !this.m_field[x, y].IsSolid);
			if(result)
			{
				float difference = (o.Bottom - ((y*WorldField.SIZE)));
				obj.CorrectPosition = new Vector2(0, obj.Position.Y -32 + 16 + 10);
			}

			return result;
		}


		#endregion

	}
}
